/******************************************************************************
*  Copyright(c) 2021-2023 Sietium
*
*  @file:    instance.cpp
*  @brif:    mult instance render
*
*  @date:    05/10/2021
*  @Author:  cary.Zhang
*
******************************************************************************/
#include <string>
#include <cstdlib>
#include <vector>
#include <array>
#include <memory>
#include "vmath.h"
#include "glad/gl-headers.h"

#include <model.h>
#include <meshgrid.h>
#include "glslprogram.h"
#include "node.h"
#include "texture.h"
#include "objectfactory.h"

class Instance: public Node
{
public:
    Instance();
protected:
    bool startup();
    void render(double currentTime, double difTime);
    void shutdown();
protected:
    GLSLProgram programGsLayers;
    GLSLProgram programShowLayers;

    GLuint vao;

    GLuint layeredFbo;
    GLuint arrayTexture;
    GLuint arrayDepth;

    std::unique_ptr<Model> model;
    std::unique_ptr<UniformBuffer<vmath::mat4, 17>> uniformBuffer;

    GLuint tbo;
    GLuint tboTexture;
    const vmath::vec4 colors[16] = {
        vmath::vec4(0.0f, 0.0f, 1.0f, 1.0f), vmath::vec4(0.0f, 1.0f, 0.0f, 1.0f),
        vmath::vec4(0.0f, 1.0f, 1.0f, 1.0f), vmath::vec4(1.0f, 0.0f, 1.0f, 1.0f),
        vmath::vec4(1.0f, 1.0f, 0.0f, 1.0f), vmath::vec4(1.0f, 1.0f, 1.0f, 1.0f),
        vmath::vec4(0.0f, 0.0f, 0.5f, 1.0f), vmath::vec4(0.0f, 0.5f, 0.0f, 1.0f),
        vmath::vec4(0.0f, 0.5f, 0.5f, 1.0f), vmath::vec4(0.5f, 0.0f, 0.0f, 1.0f),
        vmath::vec4(0.5f, 0.0f, 0.5f, 1.0f), vmath::vec4(0.5f, 0.5f, 0.0f, 1.0f),
        vmath::vec4(0.5f, 0.5f, 0.5f, 1.0f), vmath::vec4(1.0f, 0.5f, 0.5f, 1.0f),
        vmath::vec4(0.5f, 1.0f, 0.5f, 1.0f), vmath::vec4(0.5f, 0.5f, 1.0f, 1.0f)
    };
};

Instance::Instance()
{
    nodeName = "Instance";
}

bool Instance::startup(void)
{
    // glMapBufferRange added in 3.0
    // glDrawArraysInstanced added in 3.1
    // glFramebufferTexture added in 3.2
    // glTexStorage3D added in 4.2 or ARB_texture_storage
    // Binding points in 4.2 or GL_ARB_shading_language_420pack
    // gl_Layer in 4.3 or GL_ARB_fragment_layer_viewport
    if (PerfWindow::glVersion < 430 &&
        (PerfWindow::glVersion < 400 ||
         !GLAD_GL_ARB_texture_storage ||
         !GLAD_GL_ARB_shading_language_420pack ||
         !GLAD_GL_ARB_fragment_layer_viewport)) {
        printf("Failed! The GL version is less than 4.3 or 4.0 without extensions:!\n"
            "GL_ARB_texture_storage\n"
            "GL_ARB_shading_language_420pack\n"
            "GL_ARB_fragment_layer_viewport\n");
        return false;
    }


    GLint fragMaxAtomic = 0;
    glGetIntegerv(GL_MAX_FRAGMENT_ATOMIC_COUNTERS, &fragMaxAtomic);
    if (fragMaxAtomic <= 0) {
        std::cout << nodeName << " failed : GL_MAX_FRAGMENT_ATOMIC_COUNTERS is 0" << std::endl;
        return false;
    }
    
    if (PerfWindow::glVersion > 400) {
        if (!programShowLayers.build("../media/shaders/instance/430/showlayers.vs.glsl",
                                    "../media/shaders/instance/430/showlayers.fs.glsl"))
            return false;

        if (!programGsLayers.build("../media/shaders/instance/430/instance.vs.glsl",
                                "../media/shaders/instance/430/instance.fs.glsl",
                                "../media/shaders/instance/430/instance.gs.glsl"))
            return false;
    }
    else {
        if (!programShowLayers.build("../media/shaders/instance/400/showlayers.vs.glsl",
                                    "../media/shaders/instance/400/showlayers.fs.glsl"))
            return false;

        if (!programGsLayers.build("../media/shaders/instance/400/instance.vs.glsl",
                                "../media/shaders/instance/400/instance.fs.glsl",
                                "../media/shaders/instance/400/instance.gs.glsl"))
            return false;
    }
    glGenVertexArrays(1, &vao);
    glBindVertexArray(vao);

    glBindVertexArray(0);
    glEnable(GL_CULL_FACE);
    glCullFace(GL_BACK);

    model.reset(new Model);

    model->loadModel("../media/objects/bunny.obj");

    auto update = [](float value, vmath::mat4 * mat, int count) {
        mat[0] = vmath::perspective(50.0f, 1.0f, 0.1f, 1000.0f);
        for (int i = 0; i < 16; i++) {
            float fi = (float)(i + 12) / 16.0f;
            mat[i + 1] = vmath::translate(0.0f, 0.0f, -4.0f) *
                         vmath::rotate(value * 25.0f * fi, 0.0f, 0.0f, 1.0f) *
                         vmath::rotate(value * 30.0f * fi, 1.0f, 0.0f, 0.0f);
        }
    };

    uniformBuffer.reset(new UniformBuffer<vmath::mat4, 17>(update, 0));

    glGenTextures(1, &arrayTexture);
    glBindTexture(GL_TEXTURE_2D_ARRAY, arrayTexture);
    glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_RGBA8, 256, 256, 16);

    glGenTextures(1, &arrayDepth);
    glBindTexture(GL_TEXTURE_2D_ARRAY, arrayDepth);
    glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_DEPTH_COMPONENT32F, 256, 256, 16);

    glGenFramebuffers(1, &layeredFbo);
    glBindFramebuffer(GL_FRAMEBUFFER, layeredFbo);
    glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, arrayTexture, 0);
    glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, arrayDepth, 0);

    glGenBuffers(1, &tbo);
    glBindBuffer(GL_TEXTURE_BUFFER, tbo);
    glBufferData(GL_TEXTURE_BUFFER, sizeof(colors), colors, GL_STATIC_DRAW);
    glGenTextures(1, &tboTexture);
    glBindTexture(GL_TEXTURE_BUFFER, tboTexture);
    glTexBuffer(GL_TEXTURE_BUFFER, GL_RGBA32F, tbo);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindTexture(GL_TEXTURE_BUFFER, 0);
    return true;
}

void Instance::shutdown(void)
{
    glDisable(GL_CULL_FACE);
    glCullFace(GL_BACK);
    programGsLayers.clear();
    programShowLayers.clear();
    model->clearModel();

    glDeleteVertexArrays(1, &vao);

    delete uniformBuffer.release();
    glDeleteTextures(1, &arrayTexture);
    glDeleteTextures(1, &arrayDepth);
    glDeleteFramebuffers(1, &layeredFbo);

    glDeleteBuffers(1, &tbo);
    glDeleteTextures(1, &tboTexture);
}

void Instance::render(double currentTime, double difTime)
{
    (void)difTime;

    uniformBuffer->update(currentTime);

    const GLenum ca0 = GL_COLOR_ATTACHMENT0;
    const GLenum buf_b = GL_BACK;

    glBindFramebuffer(GL_FRAMEBUFFER, layeredFbo);

    glDrawBuffers(1, &ca0);
    glViewport(0, 0, 256, 256);

    static const GLfloat black[] = { 0.2f, 0.2f, 0.2f, 1.0f };
    glClearBufferfv(GL_COLOR, 0, black);

    static const GLfloat one = 1.0f;
    glClearBufferfv(GL_DEPTH, 0, &one);
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LEQUAL);

    programGsLayers.use();
    glBindVertexArray(vao);
    glBindTexture(GL_TEXTURE_BUFFER, tboTexture);

    model->renderModel();

    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    glDrawBuffers(1, &buf_b);

    programShowLayers.use();

    glViewport(0, 0, getWidth(), getHeight());

    static const GLfloat gray[] =  { 0.25f, 0.25f, 0.25f, 1.0f };
    glClearBufferfv(GL_COLOR, 0, gray);

    glBindTexture(GL_TEXTURE_2D_ARRAY, arrayTexture);
    glDisable(GL_DEPTH_TEST);

    glBindVertexArray(vao);
    glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, 16);
    glBindTexture(GL_TEXTURE_2D_ARRAY, 0);
    glBindTexture(GL_TEXTURE_BUFFER, 0);

}

REGISTER_OBJECT(Node, Instance)

